#!/bin/bash
# 修复防火墙规则冲突(端口被误封)的智能运维脚本
FIX_CODE_ID="firewall-conflict"

# 定义常用服务端口
COMMON_PORTS=(
    "22:ssh"
    "80:http"
    "443:https"
    "53:dns"
    "21:ftp"
    "25:smtp"
    "110:pop3"
    "143:imap"
    "3306:mysql"
    "5432:postgresql"
    "6379:redis"
    "8080:web-proxy"
    "8443:https-alt"
)

# 备份文件函数
backup_file() {
    local file="$1"
    if [ -f "$file" ]; then
        local backup="${file}.bak.$(date +%Y%m%d%H%M%S)"
        cp -a "$file" "$backup"
        echo "已备份配置文件: $backup"
    fi
}

# 检测系统使用的防火墙工具
detect_firewall() {
    if command -v ufw &> /dev/null; then
        echo "ufw"
        return 0
    elif command -v firewall-cmd &> /dev/null; then
        echo "firewalld"
        return 0
    elif [ -f "/etc/sysconfig/iptables" ]; then
        echo "iptables"
        return 0
    else
        echo "unknown"
        return 1
    fi
}

# 显示当前防火墙状态和规则
show_firewall_status() {
    local firewall=$1
    echo "==== 当前防火墙状态 ===="
    echo "使用的防火墙: $firewall"
    
    case $firewall in
        ufw)
            ufw status verbose
            ;;
        firewalld)
            systemctl status firewalld --no-pager
            echo -e "\n活动区域:"
            firewall-cmd --get-active-zones
            echo -e "\n开放端口:"
            firewall-cmd --list-ports
            echo -e "\n服务规则:"
            firewall-cmd --list-services
            ;;
        iptables)
            iptables -L -n -v
            echo -e "\nIPv6规则:"
            ip6tables -L -n -v 2>/dev/null
            ;;
        *)
            echo "未检测到已知的防火墙工具"
            ;;
    esac
    echo "========================"
    echo
}

# 检测端口状态
check_port_status() {
    local firewall=$1
    local port=$2
    local protocol=${3:-tcp}
    
    case $firewall in
        ufw)
            if ufw status | grep -qE "$port/tcp.*ALLOW"; then
                return 0
            else
                return 1
            fi
            ;;
        firewalld)
            if firewall-cmd --query-port="$port/$protocol"; then
                return 0
            else
                return 1
            fi
            ;;
        iptables)
            if iptables -L INPUT -n -v | grep -qE "dpt:$port.*ACCEPT"; then
                return 0
            else
                return 1
            fi
            ;;
    esac
}

# 检测并显示被封锁的常用端口
detect_blocked_ports() {
    local firewall=$1
    local blocked=()
    
    echo "检测常用端口状态..."
    for entry in "${COMMON_PORTS[@]}"; do
        port=${entry%:*}
        service=${entry#*:}
        
        if ! check_port_status "$firewall" "$port"; then
            # 检查端口是否有进程监听
            if ss -tuln | grep -q ":$port"; then
                blocked+=("$port:$service")
                echo "警告: 端口 $port ($service) 被封锁，但有进程在监听"
            fi
        fi
    done
    
    echo
    return ${#blocked[@]}
}

# 修复防火墙规则冲突
fix_firewall_conflicts() {
    local firewall=$1
    
    echo "开始修复防火墙规则冲突..."
    
    # 备份当前配置
    case $firewall in
        ufw)
            backup_file "/etc/ufw/user.rules"
            backup_file "/etc/ufw/user6.rules"
            ;;
        firewalld)
            backup_file "/etc/firewalld/firewalld.conf"
            backup_file "/etc/firewalld/zones/public.xml" 2>/dev/null
            ;;
        iptables)
            backup_file "/etc/sysconfig/iptables"
            backup_file "/etc/sysconfig/ip6tables" 2>/dev/null
            ;;
    esac
    
    # 允许所有常用端口
    for entry in "${COMMON_PORTS[@]}"; do
        port=${entry%:*}
        service=${entry#*:}
        
        if ! check_port_status "$firewall" "$port"; then
            echo "允许端口 $port ($service)..."
            case $firewall in
                ufw)
                    ufw allow "$port/tcp"
                    ufw allow "$port/udp"
                    ;;
                firewalld)
                    firewall-cmd --add-port="$port/tcp" --permanent
                    firewall-cmd --add-port="$port/udp" --permanent
                    firewall-cmd --reload
                    ;;
                iptables)
                    iptables -I INPUT -p tcp --dport "$port" -j ACCEPT
                    iptables -I INPUT -p udp --dport "$port" -j ACCEPT
                    service iptables save 2>/dev/null
                    ;;
            esac
        fi
    done
    
    # 移除可能的冲突规则（拒绝规则在允许规则之前的情况）
    case $firewall in
        ufw)
            # 重置并重新应用规则
            ufw --force reset
            for entry in "${COMMON_PORTS[@]}"; do
                port=${entry%:*}
                ufw allow "$port/tcp"
                ufw allow "$port/udp"
            done
            ufw --force enable
            ;;
        firewalld)
            # 确保默认策略合理
            firewall-cmd --set-default-zone=public
            firewall-cmd --permanent --set-target=ACCEPT
            firewall-cmd --reload
            ;;
        iptables)
            # 清除所有拒绝规则，然后重新应用允许规则
            iptables -D INPUT -j REJECT --reject-with icmp-host-prohibited 2>/dev/null
            iptables -D FORWARD -j REJECT --reject-with icmp-host-prohibited 2>/dev/null
            
            # 重新添加允许规则
            for entry in "${COMMON_PORTS[@]}"; do
                port=${entry%:*}
                iptables -I INPUT -p tcp --dport "$port" -j ACCEPT
                iptables -I INPUT -p udp --dport "$port" -j ACCEPT
            done
            
            # 添加默认拒绝规则到最后
            iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited
            iptables -A FORWARD -j REJECT --reject-with icmp-host-prohibited
            service iptables save 2>/dev/null
            ;;
    esac
    
    echo "防火墙规则修复完成"
    echo
}

# 检测并允许已使用的端口
allow_used_ports() {
    local firewall=$1
    
    echo "检测并允许正在使用的端口..."
    
    # 获取所有监听端口
    local used_ports=$(ss -tuln | awk 'NR>1 {split($5, a, ":"); print a[length(a)]}' | sort -n | uniq)
    
    for port in $used_ports; do
        # 跳过已允许的端口
        if check_port_status "$firewall" "$port"; then
            continue
        fi
        
        # 允许未被允许但正在使用的端口
        echo "允许正在使用的端口: $port"
        case $firewall in
            ufw)
                ufw allow "$port/tcp"
                ufw allow "$port/udp"
                ;;
            firewalld)
                firewall-cmd --add-port="$port/tcp" --permanent
                firewall-cmd --add-port="$port/udp" --permanent
                firewall-cmd --reload
                ;;
            iptables)
                iptables -I INPUT -p tcp --dport "$port" -j ACCEPT
                iptables -I INPUT -p udp --dport "$port" -j ACCEPT
                service iptables save 2>/dev/null
                ;;
        esac
    done
    
    echo "已允许所有正在使用的端口"
    echo
}

# 主逻辑
echo "开始处理防火墙规则冲突(端口被误封)问题..."
echo

# 检测防火墙类型
FIREWALL=$(detect_firewall)
if [ "$FIREWALL" = "unknown" ]; then
    echo "错误: 未检测到支持的防火墙工具(ufw/firewalld/iptables)"
    exit 1
fi

# 显示当前防火墙状态
show_firewall_status "$FIREWALL"

# 检测被封锁的常用端口
detect_blocked_ports "$FIREWALL"
local blocked_count=$?

if [ $blocked_count -gt 0 ]; then
    echo "发现 $blocked_count 个被误封的常用端口，需要修复"
    # 修复防火墙规则
    fix_firewall_conflicts "$FIREWALL"
else
    echo "未发现明显被误封的常用端口"
fi

# 允许所有正在使用的端口
allow_used_ports "$FIREWALL"

# 显示修复后的状态
echo "==== 修复后的防火墙状态 ===="
show_firewall_status "$FIREWALL"

echo "防火墙规则冲突修复操作完成"
echo "建议:"
echo "1. 验证关键服务是否可以正常访问"
echo "2. 根据实际需求，手动调整不必要开放的端口"
echo "3. 定期审计防火墙规则，确保安全性和可用性平衡"

exit 0
